#include "UnityPrefix.h"
#include "ShapeModule.h"
#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
#include "Runtime/Graphics/ParticleSystem/ParticleSystem.h"
#include "Runtime/Graphics/TriStripper.h"
#include "Runtime/Math/Vector2.h"
#include "Runtime/Math/Random/Random.h"
#include "Runtime/Geometry/ComputionalGeometry.h"
#include "Runtime/BaseClasses/IsPlaying.h"
#include "Runtime/Utilities/StrideIterator.h"
#include "Runtime/Filters/Mesh/LodMesh.h"
#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"

enum MeshDistributionMode
{
	kDistributionVertex,
	kDistributionTriangle,
};


/// This gives a random barycentric coord (on the edge of triangle)
// @TODO: Stupid: Make this in a faster way
inline Vector3f RandomBarycentricCoordEdge (Rand& rand)
{
	float u = rand.GetFloat ();
	float v = rand.GetFloat ();
	if (u + v > 1.0F)
	{
		u = 1.0F - u;
		v = 1.0F - v;
	}
	float w = 1.0F - u - v;
	
	int edge = RangedRandom(rand, 0, 2);
	if(0 == edge)
	{
		v += 0.5f * u;
		w += 0.5f * u;
		u = 0.0f;
	}
	else if(1 == edge)
	{
		u += 0.5f * v;
		w += 0.5f * v;
		v = 0.0f;
	}
	else
	{
		u += 0.5f * w;
		v += 0.5f * w;
		w = 0.0f;
	}
	
	return Vector3f (u, v, w);
}


// TODO: It could make sense to initialize in separated loops. i.e. separate position and velcoity vectors
inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, Rand& random, bool randomDirection)
{
	if(randomDirection)
		n = RandomUnitVector (random);
	
	n = NormalizeSafe(n);
	
	pos = Scale(pos, scale);

	Vector3f vel = Magnitude (ps.velocity[q]) * n;
	vel = localToWorld.MultiplyVector3 (vel);
	
	// @TODO: Sooo... why multiply point and then undo the result of it? Why not just MultiplyVector?
	pos = localToWorld.MultiplyPoint3 (pos) - localToWorld.GetPosition();
	ps.position[q] += pos;
	ps.velocity[q] = vel;
	
#if 0 // WIP code for converting to spherical
	Vector3f sp = ps.position[q];
	ps.position[q].x = Sqrt(sp.x*sp.x + sp.y*sp.y + sp.z*sp.z);
	ps.position[q].y = acosf(sp.z/ps.position[q].x);
	ps.position[q].z = acosf(sp.y/ps.position[q].x);
#endif
	
	if(ps.usesAxisOfRotation)
	{
		Vector3f tan = Cross (-n, Vector3f::zAxis);
		if (SqrMagnitude (tan) <= 0.01)
			tan = Cross (-pos, Vector3f::zAxis);
		if (SqrMagnitude (tan) <= 0.01)
			tan = Vector3f::yAxis;
		ps.axisOfRotation[q] = Normalize (tan);
	}
}

inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, ColorRGBA32& color, Rand& random, bool randomDirection)
{
	EmitterStoreData(localToWorld, scale, ps, q, pos, n, random, randomDirection);
	ps.color[q] *= color;
}

	
template<MeshDistributionMode distributionMode>
void GetPositionMesh (Vector3f& pos,
					  Vector3f& n,
					  ColorRGBA32& color,
					  const ParticleSystemEmitterMeshVertex* vertexData,
					  const int vertexCount,
					  const MeshTriangleData* triangleData,
					  const UInt32 numPrimitives,
					  float totalTriangleArea,
					  Rand& random,
					  bool edge)
{
	// position/normal of particle is vertex/vertex normal from mesh
	if(kDistributionVertex == distributionMode)
	{
		int vertexIndex = RangedRandom (random, 0, vertexCount);
		pos = vertexData[vertexIndex].position;
		n = vertexData[vertexIndex].normal;
		color = vertexData[vertexIndex].color;
	}
	else if(kDistributionTriangle == distributionMode)
	{
		float randomArea = RangedRandom(random, 0.0f, totalTriangleArea);
		float accArea = 0.0f;
		UInt32 triangleIndex = 0;
		
		for(UInt32 i = 0; i < numPrimitives; i++)
		{
			const MeshTriangleData& data = triangleData[i];
			accArea += data.area;
			if(accArea >= randomArea)
			{
				triangleIndex = i;
				break;
			}
		}
		
		const MeshTriangleData& data = triangleData[triangleIndex];
		UInt16 a = data.indices[0];
		UInt16 b = data.indices[1];
		UInt16 c = data.indices[2];
		
		Vector3f barycenter;
		if(edge)
			barycenter = RandomBarycentricCoordEdge (random);
		else
			barycenter = RandomBarycentricCoord (random);
		
		// Interpolate vertex with barycentric coordinate
		pos = barycenter.x * vertexData[a].position + barycenter.y * vertexData[b].position + barycenter.z * vertexData[c].position;
		n =  barycenter.x * vertexData[a].normal + barycenter.y * vertexData[b].normal + barycenter.z * vertexData[c].normal;
		
		// TODO: Don't convert to floats!!!
		ColorRGBAf color1 = vertexData[a].color;
		ColorRGBAf color2 = vertexData[b].color;
		ColorRGBAf color3 = vertexData[c].color;
		color = barycenter.x * color1 + barycenter.y * color2 + barycenter.z * color3;
	}
}

static bool CompareMeshTriangleData (const MeshTriangleData& a, const MeshTriangleData& b)
{
	return (a.area > b.area);
}

static float BuildMeshAreaTable(MeshTriangleData* triData, const StrideIterator<Vector3f> vertices, const UInt16* indices, int numTriangles)
{
	float result = 0.0f;
	for(int i = 0; i < numTriangles; i++)
	{
		const UInt16 a = indices[i * 3 + 0];
		const UInt16 b = indices[i * 3 + 1];
		const UInt16 c = indices[i * 3 + 2];
		float area = TriangleArea3D (vertices[a], vertices[b], vertices[c]);
		result += area;
		
		triData[i].indices[0] = a;
		triData[i].indices[1] = b;
		triData[i].indices[2] = c;
		triData[i].area = area;
	}
	
	return result;
}

// ------------------------------------------------------------------------------------------
	
ShapeModule::ShapeModule () : ParticleSystemModule(true)
,	m_Type (kCone)
,	m_RandomDirection (false)
,	m_Angle(25.0f)
,	m_Radius(1.0f)
,	m_Length(5.0f)
,	m_BoxX(1.0f)
,	m_BoxY(1.0f)
,	m_BoxZ(1.0f)
,	m_PlacementMode(kVertex)
,	m_CachedMesh(NULL)
,	m_MeshNode (NULL)
{
}

void ShapeModule::Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t)
{	
	DebugAssert(roState.lengthInSec > 0.0001f);	
	const float normalizedT = t / roState.lengthInSec;
	DebugAssert (normalizedT >= 0.0f);
	DebugAssert (normalizedT <= 1.0f);

	Rand& random = GetRandom();

	if (m_Type == kMesh)
	{
		if(!m_CachedMesh)
			return;
	
		if(!m_CachedVertexData.size())
			return;

		if(!m_CachedTriangleData.size())
			return;

		const ParticleSystemEmitterMeshVertex* vertexData = &m_CachedVertexData[0];
		const int vertexCount = m_CachedVertexData.size();
		size_t count = ps.array_size ();
		switch(m_PlacementMode)
		{
			case kVertex:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos;
					Vector3f n;
					ColorRGBA32 color;
					GetPositionMesh<kDistributionVertex>(pos, n, color, vertexData, vertexCount, NULL, 0, m_CachedTotalTriangleArea, random, false);
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
				}
				break;
			}
			case kEdge:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos;
					Vector3f n;
					ColorRGBA32 color;
					GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, true);
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
				}
				break;
			}
			case kTriangle:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos;
					Vector3f n;
					ColorRGBA32 color;
					GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, false);
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
				}
				break;
			}
			default:
			{
				DebugAssert(0 && "PlacementMode Not Supported");
			}
		}
	}
	else
	{
		const float r = m_Radius;
	
		float a = Deg2Rad (m_Angle);
		float sinA = Sin (a);
		float cosA = Cos (a);
		float length = m_Length;
		
		const size_t count = ps.array_size ();
		switch(m_Type)
		{
			case kSphere:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos = RandomPointInsideUnitSphere (random) * r;
					Vector3f n = pos;
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
				}
				break;
			}
			case kSphereShell:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos = RandomUnitVector(random) * r;
					Vector3f n = pos;
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
				}
				break;
			}
			case kHemiSphere:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos = RandomPointInsideUnitSphere (random) * r;
					if (pos.z < 0.0f)
						pos.z *= -1.0f;
					Vector3f n = pos;
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
				}
				break;
			}
			case kHemiSphereShell:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos = RandomUnitVector (random) * r;
					if (pos.z < 0.0f)
						pos.z *= -1.0f;
					Vector3f n = pos;
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
				}
				break;
			}
			case kCone:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector2f posXY = RandomPointInsideUnitCircle (random);
					Vector2f nXY;
					if(m_RandomDirection)
						nXY = RandomPointInsideUnitCircle (random) * sinA;
					else
						nXY = Vector2f(posXY.x, posXY.y)* sinA;
					Vector3f n (nXY.x, nXY.y, cosA);
					Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false);
				}
				break;
			}
			case kConeShell:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random));
					
					Vector2f nXY;
					if(m_RandomDirection)
						nXY = RandomPointInsideUnitCircle (random) * sinA;
					else
						nXY = Vector2f(posXY.x, posXY.y)* sinA;
					Vector3f n (nXY.x, nXY.y, cosA);
					Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false);
				}
				break;
			}
			case kConeVolume:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector2f posXY = RandomPointInsideUnitCircle (random);
					Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA;
					Vector3f n (nXY.x, nXY.y, cosA);
					Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
					pos += length * Random01(random) * NormalizeSafe(n);
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
				}
				break;
			}
			case kConeVolumeShell:
			{
				for (int q = fromIndex; q < count; ++q)
				{
					Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random));
					Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA;
					Vector3f n (nXY.x, nXY.y, cosA);
					Vector3f pos = Vector3f(posXY.x * r, posXY.y * r, 0.0f);
					pos += length * Random01(random) * NormalizeSafe(n);
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
				}
				break;
			}
			case kBox:
			{
				const Vector3f extents (0.5f * m_BoxX, 0.5f * m_BoxY, 0.5f * m_BoxZ);
				for (int q = fromIndex; q < count; ++q)
				{
					Vector3f pos = RandomPointInsideCube (random, extents);
					Vector3f n = Vector3f::zAxis;
					EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
				}
			}
			break;
			default:
			{
				DebugAssert(0 && "Shape not supported"); 
			}
		}
	}
}

void ShapeModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Vector3f& emitterScale, Vector2f minMaxBounds) const
{
	DebugAssert(minMaxBounds.x <= minMaxBounds.y);
	
	switch(m_Type)
	{
		case kSphere:
		case kSphereShell:
			bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius);
			bounds.m_Min = -bounds.m_Max;
			break;
		case kHemiSphere:
		case kHemiSphereShell:
			bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius);
			bounds.m_Min = Vector3f(-m_Radius, -m_Radius, 0.0f);
			break;
		case kCone:
		case kConeShell:
			bounds.m_Max = Vector3f(m_Radius, m_Radius, 0.0f);
			bounds.m_Min = -bounds.m_Max;
			break;
		case kConeVolume:
		case kConeVolumeShell:
			{
			const float a = Deg2Rad (m_Angle);
			const float coneRadius2 = m_Radius + m_Length * Sin (a);
			const float coneLength = m_Length * Cos (a);
			bounds.m_Max = Vector3f(coneRadius2, coneRadius2, coneLength);
			bounds.m_Min = -Vector3f(coneRadius2, coneRadius2, 0.0f);
			break;
			}
		case kBox:
			bounds.m_Max = Vector3f(m_BoxX, m_BoxY, m_BoxZ) * 0.5f;
			bounds.m_Min = -bounds.m_Max;
			break;
		case kMesh:
			{
			if(m_CachedMesh)
				bounds = m_CachedMesh->GetBounds(0);
			else
				bounds = MinMaxAABB(Vector3f::zero, Vector3f::zero);
			break;
			}
		default:
			{
			AssertBreak(!"Shape not implemented.");
			}
	}

	bounds.m_Min = Scale(bounds.m_Min, emitterScale);
	bounds.m_Max = Scale(bounds.m_Max, emitterScale);

	MinMaxAABB speedBounds;

	// Cone and cone shell random direction only deviate inside the bound
	if(m_RandomDirection && (m_Type != kCone) && (m_Type != kConeShell))
	{
		speedBounds.m_Max = Vector3f::one;
		speedBounds.m_Min = -Vector3f::one;
		minMaxBounds = Abs(minMaxBounds);
	}
	else
	{
		switch(m_Type)
		{
			case kSphere:
			case kSphereShell:
			case kMesh:
				speedBounds.m_Max = Vector3f::one;
				speedBounds.m_Min = -Vector3f::one;
				break;
			case kHemiSphere:
			case kHemiSphereShell:
				speedBounds.m_Max = Vector3f::one;
				speedBounds.m_Min = Vector3f(-1.0f, -1.0f, 0.0f);
				break;
			case kCone:
			case kConeShell:
			case kConeVolume:
			case kConeVolumeShell:
			{
				const float a = Deg2Rad (m_Angle);
				const float sinA = Sin (a);
				speedBounds.m_Max = Vector3f(sinA, sinA, 1.0f);
				speedBounds.m_Min = Vector3f(-sinA, -sinA, 0.0f);
				break;
			}
			case kBox:
				speedBounds.m_Max = Vector3f::zAxis;
				speedBounds.m_Min = Vector3f::zero;
				break;
			default:
			{
				AssertBreak(!"Shape not implemented.");
			}
		}
	}

	MinMaxAABB speedBound;
	speedBound.m_Min = bounds.m_Min + speedBounds.m_Min * minMaxBounds.y;
	speedBound.m_Max = bounds.m_Max + speedBounds.m_Max * minMaxBounds.y;
	bounds.Encapsulate(speedBound);

	MinMaxAABB negSpeedBound;
	negSpeedBound.m_Min = speedBounds.m_Min * minMaxBounds.x;
	negSpeedBound.m_Max = speedBounds.m_Max * minMaxBounds.x;
	speedBound.m_Min = min(negSpeedBound.m_Min, negSpeedBound.m_Max);
	speedBound.m_Max = max(negSpeedBound.m_Min, negSpeedBound.m_Max);
	bounds.Encapsulate(speedBound);
}

void ShapeModule::CheckConsistency ()
{
	m_Type = clamp<int> (m_Type, kSphere, kMax-1);
	m_PlacementMode = clamp<int> (m_PlacementMode, kVertex, kModeMax-1);

	m_Angle = clamp(m_Angle, 0.0f, 90.0f);
	m_Radius = max(0.01f, m_Radius);
	m_Length = max(0.0f, m_Length);		
	m_BoxX = max(0.0f, m_BoxX);
	m_BoxY = max(0.0f, m_BoxY);
	m_BoxZ = max(0.0f, m_BoxZ);
}

void ShapeModule::AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState)
{
	m_MeshNode.RemoveFromList();
	m_MeshNode.SetData(system);
	m_CachedMesh = m_Mesh;
	if (m_CachedMesh != NULL)
		m_CachedMesh->AddObjectUser( m_MeshNode );
	DidModifyMeshData();

	ResetSeed(roState);
}

void ShapeModule::ResetSeed(const ParticleSystemReadOnlyState& roState)
{
	if(roState.randomSeed == 0)
		m_Random.SetSeed(GetGlobalRandomSeed ());
	else
		m_Random.SetSeed(roState.randomSeed);
}

void ShapeModule::DidDeleteMesh (ParticleSystem* system)
{
	m_CachedMesh = NULL;
}

void ShapeModule::DidModifyMeshData ()
{
	if (m_CachedMesh == NULL)
	{
		m_CachedTriangleData.resize_uninitialized(0);
		m_CachedVertexData.resize_uninitialized(0);
		m_CachedTotalTriangleArea = 0;
		return;
	}

	
	const StrideIterator<Vector3f> vertexBuffer = m_CachedMesh->GetVertexBegin();
	const UInt16* indexBuffer = m_CachedMesh->GetSubMeshBuffer16(0);
	const SubMesh& submesh = m_CachedMesh->GetSubMeshFast (0);
	if (submesh.topology == kPrimitiveTriangleStripDeprecated)
	{
		const int numTriangles = CountTrianglesInStrip(indexBuffer, submesh.indexCount);
		const int capacity = numTriangles * 3;
		UNITY_TEMP_VECTOR(UInt16) tempIndices(capacity);
		Destripify(indexBuffer, submesh.indexCount, &tempIndices[0], capacity);
		m_CachedTriangleData.resize_uninitialized(numTriangles);
		m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, &tempIndices[0], numTriangles);
	}
	else if (submesh.topology == kPrimitiveTriangles)
	{
		const int numTriangles = submesh.indexCount/3;
		m_CachedTriangleData.resize_uninitialized(numTriangles);
		m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, indexBuffer, numTriangles);
	}
	else
	{
		m_CachedMesh = NULL;
	}

	// Optimization: This sorts so big triangles comes before small, which means finding the right triangle is faster
	std::sort(m_CachedTriangleData.begin(), m_CachedTriangleData.begin() + m_CachedTriangleData.size(), CompareMeshTriangleData);

	// Cache vertices
	const int vertexCount = m_CachedMesh->GetVertexCount();
	const StrideIterator<Vector3f> vertices = m_CachedMesh->GetVertexBegin();
	const StrideIterator<Vector3f> normals = m_CachedMesh->GetNormalBegin();
	const StrideIterator<ColorRGBA32> colors = m_CachedMesh->GetColorBegin();
	m_CachedVertexData.resize_uninitialized(vertexCount);
	for(int i = 0; i < vertexCount; i++)
	{
		m_CachedVertexData[i].position = vertices[i];

		if(!normals.IsNull())
			m_CachedVertexData[i].normal = normals[i];
		else
			m_CachedVertexData[i].normal = Vector3f::zero;
	
		if(!colors.IsNull())
			m_CachedVertexData[i].color = colors[i];
		else
			m_CachedVertexData[i].color = ColorRGBA32(0xffffffff);
	}
}
		
Rand& ShapeModule::GetRandom()
{
#if UNITY_EDITOR	
	if(!IsWorldPlaying())
		return m_EditorRandom;
	else
#endif
	return m_Random;
}
	
template<class TransferFunction>
void ShapeModule::Transfer (TransferFunction& transfer)
{
	transfer.SetVersion(2);
	ParticleSystemModule::Transfer (transfer);
	transfer.Transfer (m_Type, "type");
	
	// Primitive
	transfer.Transfer(m_Radius, "radius");
	transfer.Transfer(m_Angle, "angle");
	transfer.Transfer(m_Length, "length");
	transfer.Transfer(m_BoxX, "boxX");
	transfer.Transfer(m_BoxY, "boxY");
	transfer.Transfer(m_BoxZ, "boxZ");
	
	// Mesh
	transfer.Transfer (m_PlacementMode, "placementMode");
	TRANSFER (m_Mesh);

	transfer.Transfer (m_RandomDirection, "randomDirection"); transfer.Align();
	
	// In Unity 3.5 all cone emitters had random direction set to false, but behaved as if it was true
	if(transfer.IsOldVersion(1))
		if(kCone == m_Type)
			m_RandomDirection = true;
}

INSTANTIATE_TEMPLATE_TRANSFER(ShapeModule)

